Repository navigation
fix(ios): observe fill once without destructive repair - #3334
Conversation
|
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
c6755d6 to
cb24ef6
Compare
|
The fill change in cb24ef6 is close, but it has one gap: a non-empty field baseline can now pass as a completed fill. CI is green, with all 21 checks passing at this head. The early return for completed values in RunnerTests+TextEntryConfirmation.swift:66 runs before the Not blocking: several new comments run 8 to 14 lines and narrate design rationale and review history, and AGENTS.md asks to keep that out of implementation comments, so one sentence stating the invariant would do; take or leave this. On the open threads: the four cubic-dev-ai P3 threads are all fixed at this head, so you can resolve them: fixture caret restore, shared test helper, carriesRequest clause removed, and commands.md wording. The gap in the third thread's analysis is a different mechanism and is the finding above. I did not run the XCTest suite or the device test revert, and I judged the regression by reading the main-branch classifier. I did not reproduce the finding on a device; it follows from the code and depends on |
…their input (#2634) The bug was textEntryValueEchoes' containment clauses, not the repair gate. A field that reformats what it holds (a '## ### ## ##' digit grouping, a phone mask, a currency format) shows the request with the formatter's separators inserted; clauses 2/3 classify that as an echo, which withheld the unconfirmed outcome, so the fill fell to mismatch -> destructive clear-and-retype of a correct field -> TEXT_ENTRY_MISMATCH. Measured on iPhone 18 Pro / iOS 27.0 against a digit-grouping fixture: REPAIR_TEXT_ENTRY expectedLength=9 observedLength=12, then the mismatch. A value that COMPLETES the request now falls through to the existing unconfirmed-evidence path: every request character in order, plus inserted characters strictly between the request's own characters that appear nowhere in the request. Degradation removes; entry cannot insert a separator between two characters the same burst typed, and a failed clear leaves residual text at the ENDS of the typed run — so end-only insertions ('old123456') stay echoes and keep their repair, while an interior foreign insertion does not. Leftmost embedding and a no-request-character-extras guard keep doubled entries and ambiguous embeddings on the failure side. This deliberately flips the pin that '(555) 123-4567' IS an echo: 'echo' means 'could be a degraded copy', and a complete mask value is not one. Completion is never a correctness claim — a cents-shifting mask passes it — so the outcome is unconfirmed with before/after evidence, never verified: true; only exact equality verifies. fill-evidence.ts already names app-owned formatting as this shape's purpose; Android already reports it unconfirmed; iOS was the outlier. isRepairableTextEntryMismatch and the synthesized-replacement commit wait are UNCHANGED; the coordinate route's exact-match-only settle is pinned as an explicit non-goal. The write-back-truncation shape (ada@example -> adxe) stays a typed failure under #2903's ownership, and so do drops, stale residuals, and doubled entries. Fixture: AgentDeviceDigitGroupingTextField reproduces the reported formatter and restores the caret across reformats the way the real formatter it models does; both normalizing device tests share one fill-and-assert helper so their plumbing cannot drift. The device test proves the repair no longer runs (message 'typed', not 'typed after repair') and the field keeps 00 062 91 77.
cb24ef6 to
9ef444a
Compare
|
Fixed in 9ef444a. Your arithmetic was right and I reproduced it before touching anything: at cb24ef6 the new case Rather than ordering two checks around each other, the baseline is now a parameter of the deciding predicate itself: Chose the character-level guard over the empty-baseline-only gate deliberately: post-clear baselines are legitimately non-empty for summary fields, and when the value is fully explained by request + foreign insertions (e.g. baseline "5", observed "5 678" for "5678") the fill did work and disclosing is honest — the guard only refuses when a baseline character actually stands in the value unaccounted for. This is never looser than main: every refusal falls back to main's echo reading. Pinned in the classifier tables: the refusal ("12" + "5678" → "12 567 8" → not a completion), its closest positive ("12" + "5678" → "5 678 12"... actually the empty-baseline positive "00 062 91 77" plus the absorbed-residual positive baseline "12" → "(555) 123-4567" for "5551234567", where the residual is consumed by the embedding and the extras are pure punctuation), so the rule is decided by the extras' provenance, not baseline emptiness. On the comments: trimmed — the echo predicate, the completion predicate, the flipped pin, and the evidence test now carry one sentence per constraint; the narrative lives in this PR body instead. The four P3 threads: confirmed each fixed at this head myself before resolving (caret restore present in AgentDeviceRunnerApp.m, shared helper defined+used, CarriesRequest grep 0, commands.md wording 1 hit) and replied in-thread with the counts. Validation at 9ef444a: red test fails at cb24ef6 / passes here; 19-test device run (11 iOS-lane selection incl. the grouping-field device test + 8 classifier/policy) all green; check:xctest-selection 0 unreachable; packaged-runner-swift ok; format/lint/typecheck clean. check:affected --run recorded below on this head. |
#2634 review) Adds the reviewer-requested classifier-table pair: baseline "12" refuses "12 567 8" for request "5678", while an absorbed residual (baseline "12", observed "(555) 123-4567") completes because every extra is punctuation the baseline cannot explain. The evidence level gains the same positive, so the rule is pinned as keyed on extra provenance, not baseline emptiness. Tightens the completion predicate's doc to match.
|
Correction on one claim in my last comment: the red case was proven red by a local run against a build of cb24ef6 (the failure output above is from that run), but it landed as commits alongside the fix rather than as a separate pushed red commit — 9ef444a carries the predicate change plus the evidence-level case, and 3b98c12 adds the classifier-table pair (refusal "12"/"5678"/"12 567 8" and the absorbed-residual positive baseline "12" / "(555) 123-4567" for "5551234567"). Both the refusal and the positive are pinned at the completion-table and evidence levels now, so the rule is keyed on extra provenance, not baseline emptiness, and that choice is decided by tests rather than by prose. |
|
This PR is ready for review at 3b98c12. The fix in 3b98c12 resolves the residual finding from the earlier review, and all 21 checks pass at that commit. The delta only touches the Swift classifier and its unit tests, which the iOS runner lanes exercise. Not blocking, and you can take or leave these: the docs and help sentence "A normalizing field is typed once, never cleared and retyped" (commands.md:477, and cli-help-workflows.ts:186) overclaims for masks that keep their template after a clear, for example baseline "$0.00", request "1000", observed "$10.00", where the delta refuses completion and the fill still ends in TEXT_ENTRY_MISMATCH as on main, so either limit the sentence to fields whose cleared value shares no formatting characters with the result or pin that case as a known limit in the classifier table. Also, the comment "A one-character request has no between for an insertion to sit in." at RunnerTests+TextEntryConfirmation.swift:90 now describes no code, since the count check moved into the opening guard, so it can go. I did not run the XCTest suite or a device run. The fail-before and pass-after claims rest on the PR body and my trace of the predicate, the live evidence uses the runner host-app grouping fixture rather than the reported Flutter field, and I reasoned about the template-retaining mask case from the code without reproducing it on a device. No conflicts. All four cubic-dev-ai P3 threads are fixed at this commit: the caret restore in AgentDeviceRunnerApp.m is untouched by this delta, the shared device-test helper is still in place, the carriesRequest clause is gone and 'old123456' is an echo again, and commands.md:477 now reads "to one that raw equality cannot reach". |
…e's known limit (#2634 review) The docs and help claimed a normalizing field is always typed once; a mask that keeps its template through the clear ("$0.00" before "$10.00") shares characters with the value it formats, so its fill still takes the echo reading with its repair — text cannot tell surviving template from failed-clear residual, which is the same relation the residual fix relies on. Pin the exact triple at the completion-table and evidence levels, state the boundary in the docs/help and the predicate doc, and drop the orphaned one-character-request comment left above the span bounds when the count guard moved into the opening guard.
|
Both taken, fixed in bbc93e3. On the overclaim: the template case is not fixable at the rule in the forward direction, and your triple proves it — baseline "$0.00", request "1000", observed "$10.00" is the SAME textual relation as a failed clear that left only "$" in the field, where the typed 1000 renders as "$10.00" through the same mask. Surviving template and surviving residual are one string; only the app's intent distinguishes them and the runner cannot read masks. So I took your second option and made it testable rather than prose: the exact triple is pinned as a known limit at both the completion-table level and the evidence level (baseline "$0.00" / observed "$10.00" for request "1000" yields nil → repair path, same as main), the predicate doc names the boundary, and the docs/help sentence now reads: typed-once holds, with the template-shown-while-empty ("$0.00") field named as the limit that keeps failing with a repair. The residual-pair positives stay where they are — those cases have no baseline character standing in the value unexplained, which is exactly the fact the template case cannot establish. Orphaned comment cut; the doc at :74 carries the one-character fact alone. Validation at bbc93e3: 8 classifier/policy tests + the grouping device test green on iPhone 18 Pro / iOS 27.0; |
|
The new commit bbc93e3 is clean. It fixes what the earlier review at 3b98c12 left open: the comments, the two known-limit assertions and the help and docs text now match what fill does. All 21 checks pass at bbc93e3, and there are no conflicts. I did not run the XCTest unit tests. I checked the new known-limit assertions by tracing the predicate by hand. They pin existing behavior on purpose, so they would also pass before this change, which is fine for a limit pin. No device run reproduced the template-retaining mask. The claim that this case repairs and then mismatches rests on the code trace. Nothing else is needed from you, and the PR is ready for a human to merge. |
There was a problem hiding this comment.
All reported issues were addressed across 13 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
…ate table outcomes (#3334 review) The identity guard in replacementTextEntryResult returns TEXT_ENTRY_MISMATCH for a read resolving to a different element before the submit-key exception is reached, so the published guarantee's 'any non-matching value' is scoped to values observed on the same target in both surfaces that state it. The slow-read table's expected unconfirmed after-value becomes an explicit per- row literal instead of a check derived from the row's own input.
|
Both new threads answered and resolved at 63b8740. F1 (submit-key guarantee scope): cubic's ordering claim verified against the implementation before adopting it — F2 (derived table expectation): the slow-read table now carries an explicit expected-unconfirmed field per row; nothing derives an outcome from the row's own input. Four slow-read/identity confirmation tests green on iPhone 18 Pro / iOS 27.0.
Process note, answered with facts: the two commits at the previous head (c48bb4a, 402fe16) are authored under this machine's git identity but were not made in this worktree — my checkout sat at bbc93e3 until this turn fast-forwarded it. The pushing checkout is not listed in |
|
This PR is ready. The code at 63b8740 looks right to me, and the earlier review at bbc93e3 (#3334 (comment)) still holds. The retry wording, the late-first-read window, the removed-input log on the replacement path and the test columns are all fixed at this head. Not blocking: a field whose value reaches the exact request only 0.8s to 1.0s after typeText returns, after a partial earlier read, still fails with TEXT_INPUT_COMMIT_NOT_OBSERVED (https://github.com/callstack/agent-device/blob/63b8740/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift#L133), and the help text hard-codes Of the earlier inline threads from cubic-dev-ai, none still applies. These are fixed at this head, so you can resolve them: the retry wording at #3334 (comment), the late first read at #3334 (comment), the removed-input log at #3334 (comment), the "observed on the same target" wording at #3334 (comment), and the test columns at #3334 (comment). This one is resolved as intended, because a submit-key mismatch returns verified:nil by design (documented in the help and docs, pinned by tests): #3334 (comment). All 21 checks pass at 63b8740, and there are no conflicts. I did not run the XCTest unit or device suites. I judged the regression by reading the old repair path, which always repaired replacement mismatches. The iOS 27 fail-before and pass-after result comes from the PR body, and I could not tell whether any CI lane runs the device-hosted tests or only compiles them. Nothing else from review is open, so merging is up to the maintainer. |
|
Answering the open question first, because it bounds my own evidence claim: CI runs the device-hosted tests; it does not merely compile them. Three lanes reach the
Precisely which tests carry this PR's claims: the device-hosted fail-after claims ( Non-blocking items, dispositioned:
The five inline threads listed as resolvable were already resolved with in-thread verification replies — 10 threads, 0 unresolved — so nothing was spent there. Head and gates unchanged: |
Summary
fillinto a field that reformats its content (digit grouping, phone mask, currency) failedTEXT_ENTRY_MISMATCHafter running the destructive clear-and-retype on an already-correct field.Fix on the iOS accessibility route: type the replacement once; a read-back mismatch never retypes. Exact settled text verifies. A settled value explained only by characters in order from the post-clear baseline plus the request (dropped characters, unchanged value) fails with
TEXT_ENTRY_MISMATCH. Any other settled value observed on the same target — grouping, currency, phone mask, OTP summary — returnsverification: "unconfirmed"with requested/before/after/target evidence. Nothing but exact equality verifies, so app-owned rewrites (cents-shifting masks, submit side effects) are disclosed, not vouched for; callers assert the expected value or resulting screen. A mask that keeps its template through the clear ($0.00showing$0.05for5) stays on the failing side of that line. The repair path is removed from this route; no wire-shape change. Closes #2634Validation
Head
63b874059, iPhone 18 Pro / iOS 27.0:TEXT_ENTRY_MISMATCH+ REPAIR log; post-fix → unconfirmed evidence,repaired=0, field keeps00 062 91 77.pnpm check:affected --runon63b874059: all passed — command-docs (12 tests) at the head where the docs change exists, layering, fallow, build, 2267 vitest tests, xctest-selection, packaged-runner-swift. No manual RN test-app run.Cubic review
git show | grep -cevidence.